﻿/*  
  Copyright 2007-2009 The NGenerics Team
 (http://code.google.com/p/ngenerics/wiki/Team)

 This program is licensed under the GNU Lesser General Public License (LGPL).  You should 
 have received a copy of the license along with the source code.  If not, an online copy
 of the license can be found at http://www.gnu.org/copyleft/lesser.html.
*/
using System;
using System.Collections.Generic;
using System.Linq;

namespace NGenerics.DataStructures.Immutable
{

    /// <summary>
    /// A few utility methods that do not need access to the private state of a <see cref="Sequence{T}"/>.
    /// </summary>
    public static class TupleExtensions
    {
        /// <summary>
        /// Returns a tuple that has the same elements as the argument but in reverse order.
        /// </summary>
        /// <typeparam name="T">The type of element contained in the tuple.</typeparam>
        /// <param name="tuple">The input tuple.</param>
        /// <returns>Returns a tuple that has the same elements as the argument but in reverse order.</returns>
        public static Sequence<T> Reverse<T>(this Sequence<T> tuple)
        {
            if (tuple == null)
                return null;

            return new Sequence<T>(Enumerable.Reverse(tuple));
        }
    }

    /// <summary>
    /// An immutable class representing a sequence of values that cannot be changed.
    /// </summary>
    /// <remarks>
    /// <para>The guarantees of this class are only as strong as those of the 
    /// contained elements of type <typeparamref name="T"/>.</para>
    /// <para>Since this class is immutable, the recipient of this class can 
    /// be assured, that under no circumstances the value, hashcode or 
    /// identity change. This can be useful in a number of scenarios, 
    /// beginning with multi-treading and the use as key in a 
    /// <see cref="Dictionary{K,V}"/>.</para>
    /// <para>
    /// The value semantics imply that two different instances of this class 
    /// which were constructed with the same arguments can only be 
    /// distinguished by using <see cref="Object.ReferenceEquals(object,object)"/>.
    /// </para>
    /// </remarks>
    /// <typeparam name="T">The type of elements this tuple contains.</typeparam>
#if (!SILVERLIGHT)
    [Serializable]
#endif
    public sealed class Sequence<T> : IList<T>
    {
        #region Globals

        /// <summary>
        /// The actual array containing all elements.
        /// </summary>
        private readonly T[] elements;

        #endregion

        #region Construction
        /// <summary>
        /// Construct tuple from generic collection.
        /// </summary>
        /// <param name="collection"></param>
        public Sequence(IEnumerable<T> collection)
        {
            elements = collection.ToArray();
        }

        /// <summary>
        /// Construct tuple from array of T
        /// </summary>
        /// <param name="inputArray">the elements which will form the new tuple</param>
        public Sequence(params T[] inputArray)
        {
            elements = new T[inputArray.Length];
            Array.Copy(inputArray, elements, inputArray.Length);
        }

        #endregion

        #region IList<T> Members
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int IndexOf(T item)
        {
            for (var i = 0; i < elements.Length; i++)
            {
                if (item.Equals(elements[i]))
                {
                    return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// By definition this structure doesn't allow inserts
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        void IList<T>.Insert(int index, T item)
        {
            throw new InvalidOperationException();
        }

        /// <summary>
        /// By definition this structure doesn't allow deletion
        /// </summary>
        /// <param name="index"></param>
        void IList<T>.RemoveAt(int index)
        {
            throw new InvalidOperationException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get
            {
                return elements[index];
            }
        }

        T IList<T>.this[int index]
        {
            get
            {
                return elements[index];
            }
            set
            {
                throw new InvalidOperationException();
            }
        }

        #endregion

        #region ICollection<T> Members

        void ICollection<T>.Add(T item)
        {
            throw new InvalidOperationException();
        }

        void ICollection<T>.Clear()
        {
            throw new InvalidOperationException();
        }

        /// <summary>
        /// Checks whether this Sequence contains the specified item.
        /// </summary>
        /// <param name="item">The object to locate</param>
        /// <returns>true, when the <paramref name="item"/> is found in this Sequence; false, else</returns>
        public bool Contains(T item)
        {
            return -1 != IndexOf(item);
        }

        /// <summary>
        /// Copies the elements of this Sequence into the specified <paramref name="array"/> starting at <paramref name="arrayIndex"/>.
        /// </summary>
        /// <param name="array">Where to copy the elements to.</param>
        /// <param name="arrayIndex">Where to start placing the elements.</param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            Array.Copy(elements, 0, array, arrayIndex, elements.Length);
        }

        /// <summary>
        /// How many items this Sequence has.
        /// </summary>
        public int Count
        {
            get { return elements.Length; }
        }

        bool ICollection<T>.IsReadOnly
        {
            get { return true; }
        }

        bool ICollection<T>.Remove(T item)
        {
            throw new InvalidOperationException();
        }

        #endregion

        #region IEnumerable<T> Members

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return ((IEnumerable<T>)elements).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return elements.GetEnumerator();
        }

        #endregion

        #region Operator Overloads

        /// <summary>
        /// Compares the two Tuples based on their contents.
        /// </summary>
        /// <param name="left">The first Tuples to compare.</param>
        /// <param name="right">The second Tuples to compare.</param>
        /// <returns>true, if both Tuples have the same contents; false, otherwise.</returns>
        public static bool operator ==(Sequence<T> left, Sequence<T> right)
        {
            // shortcut reference equality
            if (ReferenceEquals(left, right))
            {
                return true;
            }

            return ReferenceEquals(left, null) || left.Equals(right);
        }

        /// <summary>
        /// Compares the two Tuples based on their constituents.
        /// </summary>
        /// <param name="left">The first Tuples to compare.</param>
        /// <param name="right">The second Tuples to compare.</param>
        /// <returns>false, if both Tuples have the same contents; true, otherwise.</returns>
        public static bool operator !=(Sequence<T> left, Sequence<T> right)
        {
            return !(left == right);
        }

        #endregion

        #region Object Overrides

        /// <summary>
        /// Determines if two Sequences are equal. Two Sequences are equal if all of their elements are equal and in the same positions.
        /// </summary>
        /// <param name="obj">The other Sequence to check against.</param>
        /// <returns>true, if and only if the other Sequence contains the same items in the same order.</returns>
        public override bool Equals(object obj)
        {
            // shortcut reference equality
            if (ReferenceEquals(this, obj))
            {
                return true;
            }

            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() != GetType())
            {
                return false;
            }

            var other = obj as Sequence<T>;

            if (other.Count != Count)
            {
                return false;
            }

            return Range.Create(0, Count - 1).All(i => Equals(this[i], other[i]));
        }

        /// <summary>
        /// Calculates a proper hashcode for this Sequence.
        /// </summary>
        /// <returns>a hashcode for this Sequence</returns>
        public override int GetHashCode()
        {
            var result = 0;
            const int somePrime = 9725491;
            foreach (var e in elements)
            {
                result ^= e == null ? somePrime : e.GetHashCode();
            }
            return result;
        }

        /// <summary>
        /// Returns a readable representation of this Sequence.
        /// </summary>
        /// <returns>A readable representation of this Sequence.</returns>
        public override string ToString()
        {
            return "{ " + String.Join(", ", elements.Select(e => e == null ? "(null)" : e.ToString()).ToArray()) + " }";
        }

        #endregion
    }
}
